#include <SFML/Graphics/VertexArray.hpp>
#include <nice2d/application.h>
#include <nice2d/background.h>
#include <nice2d/widgets/desktop.h>
#include <nice2d/widgets/slider.h>

IMPLEMENT_OBJECT(SliderStyle, ControlStyle)

SliderStyle::SliderStyle()
{
    tickColor = sf::Color::White;

    normalBackground = Background::create(sf::Color(0, 250, 0, 128));

    buttonStyle = std::make_shared<BasicButtonStyle>();
    buttonStyle->normalBackground = Background::create(sf::Color(0, 255, 0, 240));
    buttonStyle->hoverBackground = buttonStyle->normalBackground;
    buttonStyle->pressedBackground = buttonStyle->normalBackground;
}

class SliderButton : public BasicButton
{
    friend class Slider;
public:
    SliderButton() = delete;
    SliderButton(Slider *slider)
    {
        data.slider = slider;
        data.oldMousePos.x = -1;
        data.oldMousePos.y = -1;

        auto style = std::dynamic_pointer_cast<SliderStyle>(data.slider->getControlStyle());
        setControlStyle(style->buttonStyle);

        setBackground(style->buttonStyle->normalBackground);
    }
    ~SliderButton() = default;
protected:
    void moveHorizontally(float percent)
    {
        auto style = std::dynamic_pointer_cast<SliderStyle>(data.slider->getControlStyle());
        int length = data.slider->getWidth() - style->sliderButtonWidth;
        int buttonPos = percent * length;
        int x = buttonPos;
        setLeft(x);
    }
    void moveVertically(float percent)
    {
        auto style = std::dynamic_pointer_cast<SliderStyle>(data.slider->getControlStyle());
        int length = data.slider->getHeight() - style->sliderButtonWidth;
        int buttonPos = percent * length;
        int y = buttonPos;
        setTop(y);
    }
protected:
    void adjust()
    {
        auto style = std::dynamic_pointer_cast<SliderStyle>(data.slider->getControlStyle());
        float percent = data.slider->getValuePercent();
        if (data.slider->getOrientation() == Orientation_H) {
            setSize(style->sliderButtonWidth, style->sliderButtonHeight);
            auto sliderBox = data.slider->getBoundingBox();
            auto thumbBox = getBoundingBox();
            auto pos = Entity::adjustPosition(sliderBox, {thumbBox.width, thumbBox.height}, HMode::HMode_Center,
                                              VMode::VMode_Center);
            setTop(pos.y - sliderBox.top);
            moveHorizontally(percent);
        } else {
            setSize(style->sliderButtonHeight, style->sliderButtonWidth);
            auto sliderBox = data.slider->getBoundingBox();
            auto pos = Entity::adjustPosition(sliderBox, {style->sliderButtonHeight, style->sliderButtonWidth}, HMode::HMode_Center,
                                              VMode::VMode_Center);
            setLeft(pos.x - sliderBox.left);
            moveVertically(percent);
        }
    }

    void onPositionChanged() override
    {
        adjust();
    }

    void onSizeChanged() override
    {
        adjust();
    }

    void onStyleChanged() override
    {
        auto style = std::dynamic_pointer_cast<SliderStyle>(getControlStyle());
        if (data.slider->getOrientation() == Orientation_H)
            setSize(style->sliderButtonWidth, style->sliderButtonHeight);
        else
            setSize(style->sliderButtonHeight, style->sliderButtonWidth);

        BasicButton::onStyleChanged();
    }
private:
    struct SliderButtonData {
        Slider *slider = nullptr;
        sf::Vector2i oldMousePos;
    };
    SliderButtonData data;
};

class SliderData
{
public:
    Slider *slider = nullptr;
    std::shared_ptr<SliderButton> thumb = nullptr;
    Orientation orientation = Orientation_H;
    TickPlacement tickPlacement = TickPlacement_Below;
    int tickFrequency = 16;
    bool mouseOverSliderButton = false;
    sf::VertexArray tickList;

    SliderData(Slider *thisSlider):
        slider(thisSlider)
    {
        auto desktop = GET_APPLICATION_COMPONENT(Desktop);
        auto style = desktop->getControlStyle("Slider");
        slider->setControlStyle(style);

        thumb = std::make_shared<SliderButton>(slider);
        thumb->setControlStyle(std::dynamic_pointer_cast<SliderStyle>(style)->buttonStyle);
    }
};

IMPLEMENT_OBJECT(Slider, AbstractSlider)

Slider::Slider(Orientation o):
    data(new SliderData(this))
{
    data->orientation = o;

    if (o == Orientation_H)
        data->tickPlacement = TickPlacement_Below;
    else
        data->tickPlacement = TickPlacement_Right;
}

Slider::~Slider()
{
}

Orientation Slider::getOrientation()const
{
    return data->orientation;
}

void Slider::setTickPlacement(TickPlacement place)
{
    data->tickPlacement = place;
    adjust();
}

TickPlacement Slider::getTickPlacement()const
{
    return data->tickPlacement;
}

void Slider::setTickFrequency(int frequency)
{
    if (frequency >= 48 || frequency < 3)
        return;

    data->tickFrequency = frequency;
    adjust();
}

int Slider::getTickFrequency() const
{
    return data->tickFrequency;
}

int Slider::getTrackLength()
{
    auto style = getTemplateStyle<SliderStyle>();
    if (getOrientation() == Orientation_H)
        return getWidth() - style->sliderButtonWidth;
    return getHeight() - style->sliderButtonWidth;
}

void Slider::onDrawControl(sf::RenderTarget &target, sf::RenderStates states) const
{
    AbstractSlider::onDrawControl(target, states);

    if (getTickPlacement() != TickPlacement_None)
        target.draw(data->tickList, states);

    target.draw(*data->thumb, states);
}

void Slider::adjust()
{
    data->thumb->adjust();

    float percent = getValuePercent();
    data->tickList = sf::VertexArray(sf::Lines, data->tickFrequency);

    auto style = std::dynamic_pointer_cast<SliderStyle>(getControlStyle());

    float pixelsPerUnit = getTrackLength() / static_cast<float>(data->tickFrequency);

    auto sliderBox = getBoundingBox();

    int left = sliderBox.left;
    int right = sliderBox.width;
    int top = sliderBox.top;
    int bottom = sliderBox.height;

    if (getOrientation() == Orientation_H) {
        data->thumb->moveHorizontally(percent);
        if (getTickPlacement() == TickPlacement_Below ||
                getTickPlacement() == TickPlacement_Above ||
                getTickPlacement() == TickPlacement_Center) {
            float y1 = top;
            float y2 = y1 + (sliderBox.height * 0.25f);
            if (getTickPlacement() == TickPlacement_Below) {
                y1 = bottom;
                y2 = y1 - sliderBox.height * 0.25f;
            } else if (getTickPlacement() == TickPlacement_Above) {
                y1 = 0;
                y2 = sliderBox.height * 0.25f;
            } else {
                y1 = bottom - sliderBox.height * 0.25 - top;
                y2 = sliderBox.height * 0.25f;
            }

            float x = 0;
            for (int i = 0; i < data->tickFrequency; i++) {
                sf::Vertex vertex[2];
                vertex[0].color = style->tickColor;
                vertex[1].color = style->tickColor;

                vertex[0].position = sf::Vector2f(x, y1);
                vertex[1].position = sf::Vector2f(x, y2);
                data->tickList.append(vertex[0]);
                data->tickList.append(vertex[1]);
                x += pixelsPerUnit;
            }
        }
    } else {
        data->thumb->moveVertically(percent);

        if (getTickPlacement() == TickPlacement_Left ||
                getTickPlacement() == TickPlacement_Right ||
                getTickPlacement() == TickPlacement_Center) {
            float x1 = left;
            float x2 = left + getWidth() * 0.25f;
            if (getTickPlacement() == TickPlacement_Left) {
                x1 = left;
                x2 = left + getWidth() * 0.25f;
            } else if (getTickPlacement() == TickPlacement_Right) {
                x1 = right;
                x2 = right - getWidth() * 0.25f;
            } else {
                x1 = left + getWidth() * 0.25;
                x2 = right - getWidth() * 0.25f;
            }

            float y = /*top + */getTrackLength() * 0.05;
            for (int i = 0; i < data->tickFrequency; i++) {
                sf::Vertex vertex[2];
                vertex[0].color = style->tickColor;
                vertex[1].color = style->tickColor;

                vertex[0].position = sf::Vector2f(x1, y);
                vertex[1].position = sf::Vector2f(x2, y);
                data->tickList.append(vertex[0]);
                data->tickList.append(vertex[1]);
                y += pixelsPerUnit;
            }
        }
    }
}

void Slider::onMouseEnter()
{
    if (data->thumb->isUnderMouse()) {
        data->mouseOverSliderButton = true;
        data->thumb->onMouseEnter();
    }
}

void Slider::onMouseExit()
{
    auto pos = Application::getInstance()->getMousePosition();
    if (data->mouseOverSliderButton) {
        if (!sf::Mouse::isButtonPressed(sf::Mouse::Left)) {
            data->thumb->onMouseExit();
            data->mouseOverSliderButton = false;
        } else {
            if (getOrientation() == Orientation_H) {
                int length = pos.x - getPosition().x;
                int value = length / (float)getWidth() * (getHigh() - getLow()) + getLow();
                setValue(value);
            } else {
                int length = pos.y - getPosition().y;
                int value = length / (float)getHeight() * (getHigh() - getLow()) + getLow();
                setValue(value);
            }
        }
    }
}

void Slider::onMousePressed(sf::Mouse::Button button)
{
    auto pos = Application::getInstance()->getMousePosition();
    if (!data->thumb->getControlBelow(sf::Vector2i(pos.x, pos.y)))
        data->thumb->onMousePressed(button);
}

void Slider::onMouseReleased(sf::Mouse::Button button)
{
    auto pos = Application::getInstance()->getMousePosition();
    if (!data->thumb->getControlBelow(sf::Vector2i(pos.x, pos.y)))
        data->thumb->onMouseReleased(button);
}

void Slider::onMouseWheelScroll(float scroll)
{
    int value = getValue();
    int newValue = value + getStep() * scroll;
    setValue(newValue);
}

void Slider::onMouseMoved(int x, int y)
{
    auto pos = Application::getInstance()->getMousePosition();
    bool mousePressed = sf::Mouse::isButtonPressed(sf::Mouse::Left);
    if (mousePressed) {
        if (getOrientation() == Orientation_H) {
            int length = pos.x - getPosition().x;
            int value = length / (float)getWidth() * (getHigh() - getLow()) + getLow();
            setValue(value);
        } else {
            int length = pos.y - getPosition().y;
            int value = length / (float)getHeight() * (getHigh() - getLow()) + getLow();
            setValue(value);
        }
    } else if (data->mouseOverSliderButton && !data->thumb->isUnderMouse()) {
        data->mouseOverSliderButton = false;
        data->thumb->onMouseExit();
    } else if (!data->mouseOverSliderButton && data->thumb->isUnderMouse()) {
        data->mouseOverSliderButton = true;
        data->thumb->onMouseEnter();
    }
}

void Slider::onPositionChanged()
{
    adjust();
}

void Slider::onSizeChanged()
{
    adjust();
}

void Slider::onStyleChanged()
{
    AbstractSlider::onStyleChanged();
    auto style = getTemplateStyle<SliderStyle>();
    data->thumb->setControlStyle(style->buttonStyle);
}

void Slider::onValueChanged()
{
    adjust();
}
